﻿using DotNetCommon.Data;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace DotNetCommon.Extensions;
/// <summary>
/// <see cref="System.Threading.Tasks"/>扩展类
/// </summary>
public static class TaskExtensions
{
    /// <summary>
    /// 通用异步转换方法,参照: <seealso cref="ObjectExtensions.Mapper{TFrom, TDest}(object, bool, bool)"/>, 示例:
    /// <code>
    /// var task = Task.FromResult(new Person { Id = 1 });
    /// var dto = await task.MapperAsync&lt;Person, PersonDto>();
    /// </code>
    /// </summary>
    public static async Task<TDest> MapperAsync<TFrom, TDest>(this Task<TFrom> task)
    {
        return await task.ContinueWith(t => t.Result.Mapper<TDest>());
    }

    /// <summary>
    /// 异步解开Result模型, Result.Success为true则返回模型,否则抛出ResultException异常, 示例:
    /// <code>
    /// var res = Task.FromResult(Result.Ok(1));
    /// await res.UnWrapAsync();// 得到: 1
    /// </code>
    /// </summary>
    public static async Task<T> UnWrapAsync<T>(this Task<Result<T>> task)
    {
        var t = await task.ContinueWith(t => t.Result.UnWrap());
        return t;
    }

    /// <summary>
    /// 解开Result模型, Result.Success为true则返回模型,否则抛出ResultException异常
    /// <code>
    /// var res = Task.FromResult(Result.NotOk("失败"));
    /// await res.UnWrapAsync();// 报异常 ResultException
    /// </code>
    /// </summary>
    public static async Task<object> UnWrapAsync(this Task<Result> task)
    {
        return await task.ContinueWith(t => t.Result.UnWrap());
    }

    /// <summary>
    /// 设置当前任务的最长等待时间<paramref name="timeout"/>，如果在指定的时间内还未执行完毕就报异常, 示例:
    /// <code>
    /// await new HttpClient()
    ///     .GetAsync("https://www.baidu.com")
    ///     .TimeoutAfter(TimeSpan.FromSeconds(3))
    ///     .HandleException&lt;TimeoutException>(ex => logger.Error("网络不通"));
    /// })
    /// </code>
    /// </summary>
    public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout)
    {
        await TimeoutAfterImpl(task, timeout).ConfigureAwait(false);
        return task.Result;
    }

    /// <summary>
    /// 设置当前任务集合全部执行完毕的最长等待时间<paramref name="timeout"/>，如果在指定的时间内还未执行完毕就报异常, 示例:
    /// <code>
    /// new Task[]
    /// {
    ///     Task.Run(()=>{ }),//任务1
    ///     Task.Run(()=>{ }),//任务2
    /// }.TimeoutAfter(TimeSpan.FromSeconds(5)).HandleException<TimeoutException>(ex=> logger.Error("任务执行超时"));
    /// </code>
    /// </summary>
    public static async Task<IEnumerable<Task<T>>> TimeoutAfter<T>(
        this IEnumerable<Task<T>> tasks, TimeSpan timeout)
    {
        await TimeoutAfterImpl(tasks, timeout).ConfigureAwait(false);
        return tasks;
    }

    /// <summary>
    /// 设置当前任务的最长等待时间<paramref name="timeout"/>，如果在指定的时间内还未执行完毕就报异常, 示例:
    /// <code>
    /// Task.Run(async () =>
    /// {
    ///     await new HttpClient().GetAsync("https://www.baidu.com");
    /// }).TimeoutAfter(TimeSpan.FromSeconds(3)).HandleException&lt;TimeoutException>(ex => logger.Error("网络不通"));
    /// </code>
    /// </summary>
    public static async Task TimeoutAfter(this Task task, TimeSpan timeout) =>
        await TimeoutAfterImpl(task, timeout).ConfigureAwait(false);

    /// <summary>
    /// 设置当前任务集合全部执行完毕的最长等待时间<paramref name="timeout"/>，如果在指定的时间内还未执行完毕就报异常, 示例:
    /// <code>
    /// new Task[]
    /// {
    ///     Task.Run(()=>{ }),//任务1
    ///     Task.Run(()=>{ }),//任务2
    /// }.TimeoutAfter(TimeSpan.FromSeconds(5)).HandleException<TimeoutException>(ex=> logger.Error("任务执行超时"));
    /// </code>
    /// </summary>
    public static async Task TimeoutAfter(this IEnumerable<Task> tasks, TimeSpan timeout) =>
        await TimeoutAfterImpl(tasks, timeout).ConfigureAwait(false);

    /// <summary>
    /// 等待当前集合中所有任务的返回，代码示例：
    /// <code>
    /// var res3 = await new List&lt;Task&lt;int&gt;&gt;() {
    ///     Task.Run&lt;int&gt;(()=>1),
    ///     Task.Run&lt;int&gt;(()=>2),
    ///     Task.Run&lt;int&gt;(()=>3)
    /// };
    /// //输出: 1,2,3
    /// Console.WriteLine(string.Join(",", res3));
    /// </code>
    /// </summary>
    public static TaskAwaiter<T[]> GetAwaiter<T>(this IEnumerable<Task<T>> tasks) =>
        Task.WhenAll(tasks).GetAwaiter();

    /// <summary>
    /// 等待当前集合中所有任务的返回
    /// </summary>
    /// <example>
    /// <code>
    ///     var res3 = await new List&lt;Task&gt;() {
    ///         Task.Run(()=>{}),
    ///         Task.Run(()=>{}),
    ///         Task.Run(()=>{})
    ///     };
    /// </code>
    /// </example>
    public static TaskAwaiter GetAwaiter(this IEnumerable<Task> tasks) =>
        Task.WhenAll(tasks).GetAwaiter();

    #region HandleException
    /// <summary>
    /// 尝试消化掉 Task 内部抛出的异常, 示例:
    /// <code>
    /// //处理了内部异常, 就不会再抛到外面了
    /// await Task.Run(() =>
    /// {
    ///     int i = 0;
    ///     i = 1 / i;
    ///  }).HandleExceptions((ex) =>
    ///  {
    ///     //记录异常
    ///     logger.Error(ex);
    ///  });
    /// </code>
    /// </summary>
    public static Task HandleExceptions(this Task task, Action<Exception> exceptionsHandler)
    {
        AssertUtil.NotNull(task);
        AssertUtil.NotNull(exceptionsHandler);

        return task.ContinueWith(t =>
        {
            var aggEx = t.Exception;

            if (aggEx is null) { return; }

            aggEx.Flatten().Handle(ie =>
            {
                exceptionsHandler(ie);
                return true;
            });
        },
        CancellationToken.None,
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default);
    }

    /// <summary>
    /// 尝试消化掉 Task 内部抛出的异常, 示例:
    /// <code>
    /// //处理了内部异常, 就不会再抛到外面了
    /// await Task.Run(() =>
    /// {
    ///     int i = 0;
    ///     i = 1 / i;
    ///  }).HandleExceptions(ex => ex is DivideByZeroException, ex =>
    ///  {
    ///     //记录异常
    ///     logger.Error(ex);
    ///  });
    /// </code>
    /// </summary>
    public static Task HandleExceptions(
        this Task task, Func<Exception, bool> exceptionPredicate, Action<Exception> exceptionHandler)
    {
        AssertUtil.NotNull(task);
        AssertUtil.NotNull(exceptionPredicate);
        AssertUtil.NotNull(exceptionHandler);

        return task.ContinueWith(t =>
        {
            var aggEx = t.Exception;

            if (aggEx is null) { return; }

            aggEx.Flatten().Handle(ie =>
            {
                if (exceptionPredicate(ie))
                {
                    exceptionHandler(ie);
                    return true;
                }

                return false;
            });
        },
        CancellationToken.None,
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default);
    }

    /// <summary>
    /// 尝试消化掉 Task 内部抛出的异常, 示例:
    /// <code>
    /// //处理了内部异常, 就不会再抛到外面了
    /// await Task.Run(() =>
    /// {
    ///     int i = 0;
    ///     i = 1 / i;
    ///  }).HandleException&lt;DivideByZeroException>(ex =>
    ///  {
    ///     //记录异常
    ///     logger.Error(ex);
    ///  });
    /// </code>
    /// </summary>
    public static Task HandleException<T>(this Task task, Action<T> exceptionHandler)
        where T : Exception
    {
        AssertUtil.NotNull(task);
        AssertUtil.NotNull(exceptionHandler);

        return task.ContinueWith(t =>
        {
            var aggEx = t.Exception;

            if (aggEx is null) { return; }

            aggEx.Flatten().Handle(ex =>
            {
                if (ex is T expectedException)
                {
                    exceptionHandler(expectedException);
                    return true;
                }

                return false;
            });
        },
        CancellationToken.None,
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default);
    }
    #endregion

    private static async Task TimeoutAfterImpl(this Task task, TimeSpan timeoutPeriod)
    {
        AssertUtil.NotNull(task);

        using var cts = new CancellationTokenSource();

        var timeoutTask = Task.Delay(timeoutPeriod, cts.Token);
        var finishedTask = await Task.WhenAny(task, timeoutTask).ConfigureAwait(false);

        if (finishedTask == timeoutTask)
        {
            throw new TimeoutException("Task timed out after: " + timeoutPeriod.ToString());
        }

        cts.Cancel();
    }

    private static async Task TimeoutAfterImpl(this IEnumerable<Task> tasks, TimeSpan timeoutPeriod)
    {
        AssertUtil.NotNull(tasks);

        using var cts = new CancellationTokenSource();

        var cToken = cts.Token;
        var timeoutTask = Task.Delay(timeoutPeriod, cToken);
        var tasksList = new List<Task>(tasks) { timeoutTask };

        while (tasksList.Count > 0)
        {
            var finishedTask = await Task.WhenAny(tasksList).ConfigureAwait(false);

            if (finishedTask == timeoutTask)
            {
                throw new TimeoutException("At least one of the tasks timed out after: " + timeoutPeriod.ToString());
            }

            tasksList.Remove(finishedTask);

            if (tasksList.Count == 1 && tasksList[0] == timeoutTask) { break; }
        }

        cts.Cancel();
    }
}